// https://promisesaplus.com/
// reference https://github.com/cuipengfei/Spikes/blob/master/js/promise/RWPromise.js
// 1.1 "promise" is an object or function with a then method whose behavior conforms to this specification.
function PromiseV0 () {
  var states = {
    pending: 1,
    fulfilled: 2,
    rejected: 3
  };
  var me = this;
  me.callbacks = [];
  me.state = states.pending;
  // ???????
  function isFunc (fn) {
    return typeof fn === 'function';
  }
  function transition(state, x) {
    // 2.1.1.1 may transition to either the fulfilled or rejected state.
    // 2.1.2.1 must not transition to any other state.
    // 2.1.3.1 must not transition to any other state.
    if (me.state === states.pending) {
      // 2.1.1.1
      me.state = state;
      // 2.1.2.2 must have a value, which must not change.
      // 2.1.3.2 must have a value, which must not change.
      me.x = x;
      me.callbacks.forEach(function (callBack) {
        // 2.2.6.1 If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
        // 2.2.6.2 If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
        callBack();
      })
    }
  }

  me.resolve = function (value) {
    transition(states.fulfilled, value);
  };

  me.reject = function (reason) {
    transition(states.rejected, reason);
  };

  me.then = function (onFulfilled, onRejected) {
    var resultPromise = new PromiseV0();

    function schedulePromise2Resolution () {
      // 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
      setTimeout(function () {
        try {
          // 2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
          onFulfilled = isFunc(onFulfilled) ? onFulfilled : function (v) {
            return v;
          };
          // 2.2.7.3 If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.
          onRejected = isFunc(onRejected) ? onRejected : function (reason) {
            throw reason;
          };
          var x = me.state === states.fulfilled ? onFulfilled(me.x) : onRejected(me.x);
          // 2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
          resolutionProcedure(resultPromise, x);
        } catch (e) {
          // 2.2.7.2 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
          resultPromise.reject(e);
        }
      })
    }
    if (me.state === states.pending) {
      self.callbacks.push(schedulePromise2Resolution);
    } else {
      schedulePromise2Resolution();
    }
    // 2.2.7 then must return a promise.
    return resultPromise;
  };
  function resolutionProcedure (promise, x) {
    // 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
    var thenCalledOrThrow = false;
    var then;
    // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
    if (promise === x) {
      promise.reject(new TypeError('Spec 2.3.1, promise and x should not be the same object'));
    }
    // 2.3.3 Otherwise, if x is an object or function,
    if ((x !== null) && (typeof x === 'object') || isFunc(x)) {
      try {
        // 2.3.3.1 Let then be x.then.
        then = x.then;
        if (isFunc(then)) {
          // 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where
          then.call(x, function resolvePromise(y) {
            thenCalledOrThrow = true;
            // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
            resolutionProcedure(promise, y);
          }, function rejectPromise(r) {
            thenCalledOrThrow = true;
            // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
            promise.reject(r);
          })
        } else {
          // 2.3.3.4 If then is not a function, fulfill promise with x.
          promise.resolve(x);
        }
      } catch (e) {
        // 2.3.3.3.4 If calling then throws an exception e,

        // 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
        if (!thenCalledOrThrow) {
          thenCalledOrThrow = true;
          // 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
          // 2.3.3.3.4.2 Otherwise, reject promise with e as the reason.
          promise.reject(e);
        }
      }
    } else {
      // 2.3.4 If x is not an object or function, fulfill promise with x.
      promise.resolve(x);
    }
  }
}

module.exports = PromiseV0;
